using System;
using System.Collections.Generic;
using System.Linq;
using ExcelLibrary.SpreadSheet;
using HappyBa.Core.Data;
using HappyBa.Core.UserReport;
using HappyBa.Localization;

namespace HappyBa.Core.Reporting
{
    class UsersReportGenerator : IUsersReportGenerator
    {
        private readonly IReportFilePathGetter pathGetter;

        public UsersReportGenerator(IReportFilePathGetter pathGetter)
        {
            this.pathGetter = pathGetter;
        }

        public void Build(UserReportContext context, IList<DayUserReport> report)
        {
            pathGetter.Build(x => Create(x, context, report));
        }

        private void Create(string fileName, UserReportContext context, IList<DayUserReport> report)
        {
            var workbook = new Workbook();
            var worksheet = new Worksheet(HappyBaLang.ExcelReport);
            worksheet.Cells[1, 0] = new Cell(HappyBaLang.Date);
            var lineNo = 0;
            if (context.Settings.ReportByDaysAndUsers)
            {
                lineNo = PrintHeaders(context, worksheet, 0);
                lineNo = BuildDays(context, report, worksheet, lineNo);
            }
            if (context.Settings.ReportByUsers)
            {
                lineNo = PrintHeaders(context, worksheet, lineNo + 2);
                lineNo = BuildUsersSummary(context, report, worksheet, lineNo);
            }
            if (context.Settings.ReportTotal)
            {
                lineNo = BuildSummary(context, report, worksheet, lineNo+2);
            }

            workbook.SaveResults(fileName, lineNo, worksheet);
        }

        private static int PrintHeaders(UserReportContext context, Worksheet worksheet, int lineNo)
        {
            var id = 1;
            foreach (var user in context.Users)
            {
                worksheet.Cells[lineNo, id] = new Cell(user);
                if (context.Settings.Jobs)
                {
                    worksheet.Cells[lineNo + 1, id] = new Cell(HappyBaLang.Jobs);
                    ++id;
                }
                if (context.Settings.Tasks)
                {
                    worksheet.Cells[lineNo + 1, id] = new Cell(HappyBaLang.Tasks);
                    ++id;
                }
            }
            return lineNo += 2;
        }

        private static int BuildDays(UserReportContext context, IEnumerable<DayUserReport> report, Worksheet worksheet, int lineNo)
        {
            foreach (var day in report)
            {
                worksheet.Cells[lineNo, 0] = worksheet.BuildCell(day.Date.ToShortDateString());
                var i = 1;
                var maxExtra = 1;
                var maximums = new List<int>();
                foreach (var user in context.Users)
                {
                    var userReport = day.Reports.FirstOrDefault(x => string.Equals(user, x.User));
                    var max = 0;
                    if (userReport != null && userReport.Tasks.Any())
                    {
                        max = BuildTasksAndJobs(context, worksheet, lineNo, userReport.Tasks, i);
                    }
                    if (max > maxExtra) maxExtra = max;
                    maximums.Add(max);
                    i += GetColumnsCount(context);
                }
                PrintWhitespaces(worksheet, lineNo, maximums, maxExtra, GetColumnsCount(context));
                lineNo += maxExtra;
            }
            return lineNo;
        }

        private static void PrintWhitespaces(Worksheet worksheet, int lineNo, List<int> maximums, int maxExtra, int columns)
        {
            for (var i = 0; i < maximums.Count; ++i)
            {
                var max = maximums[i];
                for(;max < maxExtra;++max )
                {
                    worksheet.Cells[lineNo + max, 1 + i*columns] = worksheet.BuildCell(string.Empty);
                }
            }
        }

        private static int GetColumnsCount(UserReportContext context)
        {
            return (context.Settings.Jobs ? 1 : 0) + (context.Settings.Tasks ? 1 : 0);
        }

        private static int BuildUsersSummary(UserReportContext context, IList<DayUserReport> report, Worksheet worksheet, int lineNo)
        {
            worksheet.Cells[lineNo-1, 0] = worksheet.BuildCell(HappyBaLang.TotalByUsers);
            var i = 1;
            var maxExtra = 1;
            var maximums = new List<int>();
            foreach (var user in context.Users)
            {
                var max = 0;
                var tasks = report.SelectMany(x => x.Reports.Select(r => r))
                    .Where(x => x.User == user)
                    .SelectMany(x => x.Tasks)
                    .ToArray();
                if (tasks.Any())
                {
                    max = BuildTasksAndJobs(context, worksheet, lineNo, tasks, i);
                }
                if (max > maxExtra) maxExtra = max;
                maximums.Add(max);
                i += GetColumnsCount(context);
            }
            PrintWhitespaces(worksheet, lineNo, maximums, maxExtra, GetColumnsCount(context));
            lineNo += maxExtra;
            return lineNo;
        }

        private static int BuildSummary(UserReportContext context, IList<DayUserReport> report, Worksheet worksheet, int lineNo)
        {
            PrintSummaryHeaders(context, worksheet, lineNo);
            lineNo++;

            var maxExtra = 1;
            var tasks = report.SelectMany(x => x.Reports.Select(r => r))
                .SelectMany(x => x.Tasks)
                .ToArray();
            if (tasks.Any())
            {
                maxExtra = BuildTasksAndJobs(context, worksheet, lineNo, tasks, 1);
            }
            lineNo += maxExtra;
            return lineNo;
        }

        private static void PrintSummaryHeaders(UserReportContext context, Worksheet worksheet, int lineNo)
        {
            var x = 0;
            worksheet.Cells[lineNo, x] = worksheet.BuildCell(HappyBaLang.Total);
            if (context.Settings.Jobs)
            {
                worksheet.Cells[lineNo, ++x] = worksheet.BuildCell(HappyBaLang.Jobs);
            }
            if (context.Settings.Tasks)
            {
                worksheet.Cells[lineNo, ++x] = worksheet.BuildCell(HappyBaLang.Tasks);
            }
        }

        private class Item
        {
            public string Key { get; set; }
            public IList<TaskReport> Tasks { get; set; }
        }

        private static int BuildTasksAndJobs(UserReportContext context, Worksheet worksheet, int lineNo, IList<TaskReport> tasks, int i)
        {
            var extra = 0;
            var delta = 0;
            var maxExtra = 1;
            if (context.Settings.Jobs)
            {
                foreach (var job in GroupJobs(tasks, context))
                {
                    worksheet.Cells[lineNo + extra, i + delta] = worksheet.BuildCell(string.Format("[{0:0.##}] {1}", job.Tasks.Sum(x => x.Time), job.Key));
                    ++extra;
                    if (extra > maxExtra) maxExtra = extra;
                }
                delta = 1;
            }
            if (context.Settings.Tasks)
            {
                extra = 0;
                foreach (var task in GroupTasks(tasks, context))
                {
                    worksheet.Cells[lineNo + extra, i + delta] = worksheet.BuildCell(string.Format("[{0:0.##}] {1}", task.Tasks.Sum(x => x.Time), task.Key));
                    ++extra;
                    if (extra > maxExtra) maxExtra = extra;
                }
            }
            return maxExtra;
        }

        private static IEnumerable<Item> GroupTasks(IList<TaskReport> tasks, UserReportContext context)
        {
            if (!context.Settings.GroupTasks)
            {
                return tasks
                    .Select(x => new Item
                    {
                        Key = x.Task.ToString(),
                        Tasks = new[] { x }
                    });
            }
            return tasks.GroupBy(x => x.Task.ToString())
                .Select(x=>new Item
                {
                    Key = x.Key,
                    Tasks = x.ToArray()
                })
                .OrderByDescending(x=>x.Key);
        }

        private static IEnumerable<Item> GroupJobs(IList<TaskReport> tasks, UserReportContext context)
        {
            if (!context.Settings.GroupJobs)
            {
                return tasks
                    .Select(x => new Item
                    {
                        Key = GetJobCodeField(x.Task),
                        Tasks = new[] {x}
                    });
            }
            return tasks.GroupBy(x => GetJobCodeField(x.Task))
                .Select(x=>new Item
                {
                    Key = x.Key,
                    Tasks = x.ToArray()
                })
                .OrderByDescending(x => x.Key);
        }

        private static string GetJobCodeField(EntityBase entity)
        {
            var field = entity.CustomFields
                    .Where(x => string.Equals("job", x.Name, StringComparison.OrdinalIgnoreCase) || string.Equals("Job (more)", x.Name, StringComparison.OrdinalIgnoreCase))
                    .FirstOrDefault(x => !string.IsNullOrEmpty(x.Value));
            return field == null ? string.Empty : field.Value;
        }
    }
}